/*
 * Copyright 2009 (c) Patrick Pietens
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
package indigo.utils
{
	import flash.utils.ByteArray;
	/**
	 * @author patrickpietens
	 *
	 */
	public class Base64 	{
		private static const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

		
		public static function encodeString(data:String):String 		{
			var myBytes:ByteArray = new ByteArray( );
			myBytes.writeUTFBytes( data );
			
			return encodeByteArray( myBytes );
		}

		
		public static function encodeByteArray(data:ByteArray):String 		{
			// Set local properties
			var i:int;
			var myOutput:String = "";
			var myDataBuffer:Array;
			var myOutputBuffer:Array = new Array( 4 );
			
			// Rewind ByteArray
			data.position = 0;
			
			// while there are still bytes to be processed
			while ( data.bytesAvailable > 0 ) 			{
				
				// Create new data buffer and populate next 3 bytes from data
				myDataBuffer = new Array( );
				for ( i = 0; i < 3 && data.bytesAvailable > 0 ; i++ ) 					myDataBuffer[ i ] = data.readUnsignedByte( );
				
				// Convert to data buffer Base64 character positions and 
				// store in output buffer
				myOutputBuffer[ 0 ] = ( myDataBuffer[ 0 ] & 0xfc ) >> 2;
				myOutputBuffer[ 1 ] = ( ( myDataBuffer[ 0 ] & 0x03 ) << 4) | ( ( myDataBuffer[ 1 ] ) >> 4 );
				myOutputBuffer[ 2 ] = ( ( myDataBuffer[ 1 ] & 0x0f ) << 2) | ( ( myDataBuffer[ 2 ] ) >> 6 );
				myOutputBuffer[ 3 ] = myDataBuffer[ 2 ] & 0x3f;
				
				// If data buffer was short (i.e not 3 characters) then set
				// end character indexes in data buffer to index of '=' symbol.
				// This is necessary because Base64 data is always a multiple of
				// 4 bytes and is basses with '=' symbols.
				for ( i = myDataBuffer.length; i < 3 ; i++) 					myOutputBuffer[ i + 1 ] = 64;
				
				// Loop through output buffer and add Base64 characters to 
				// encoded data string for each character.
				for ( i = 0; i < myOutputBuffer.length ; i++) 					myOutput += BASE64_CHARS.charAt( myOutputBuffer[ i ] );
			}
			
			// Return encoded data
			return myOutput;
		}

		
		public static function decodeToString(data:String):String 		{
			var myBytes:ByteArray = decodeToByteArray( data );
			return myBytes.readUTFBytes( myBytes.length );
		}

		
		public static function decodeToByteArray(data:String):ByteArray 		{
			// Set initial properties
			var i:int;
			var j:int;
			
			var myOutput:ByteArray = new ByteArray( );
			var myDataBuffer:Array = new Array( 4 );
			var myOutputBuffer:Array = new Array( 3 );

			// While there are data bytes left to be processed
			for ( i = 0; i < data.length ; i += 4) 			{
				// Populate data buffer with position of Base64 characters for
				// next 4 bytes from encoded data
				for ( j = 0; j < 4 && i + j < data.length ; j++ ) 					myDataBuffer[ j ] = BASE64_CHARS.indexOf( data.charAt( i + j ) );
      			
				// Decode data buffer back into bytes
				myOutputBuffer[ 0 ] = ( myDataBuffer[ 0 ] << 2 ) + ( ( myDataBuffer[ 1 ] & 0x30 ) >> 4 );
				myOutputBuffer[ 1 ] = ( ( myDataBuffer[ 1 ] & 0x0f ) << 4 ) + ( ( myDataBuffer[ 2 ] & 0x3c ) >> 2 );		
				myOutputBuffer[ 2 ] = ( ( myDataBuffer[ 2 ] & 0x03 ) << 6 ) + myDataBuffer[ 3 ];
				
				// Add all non-padded bytes in output buffer to decoded data
				for ( j = 0; j < myOutputBuffer.length ; j++) 				{
					if (myDataBuffer[ j + 1 ] == 64) break;
					myOutput.writeByte( myOutputBuffer[ j ] );
				}
			}
			
			// Rewind decoded data ByteArray
			myOutput.position = 0;
			
			// Return decoded data
			return myOutput;
		}		}
}